/**
 * 在data中的offset位置开始写入str字符串
 */
const writeString = (data: DataView, offset: number, str: string): void => {
  for (let i: number = 0; i < str.length; i++)
    data.setUint8(offset + i, str.charCodeAt(i));
}

/**
 * 数据合并压缩
 * 根据输入和输出的采样率压缩数据，
 * 比如输入的采样率是48k的，我们需要的是（输出）的是16k的，由于48k与16k是3倍关系，
 * 所以输入数据中每隔3取1位
 * @param data - [-1, 1]的pcm数据
 * @param inputSampleRate - 输入采样率
 * @param outputSampleRate - 输出采样率
 * @returns 压缩处理后的二进制数据
 */
const compress = (
  data: {
    leftData: Float32Array;
    rightData: Float32Array;
  },
  inputSampleRate: number,
  outputSampleRate: number,
): Float32Array => {
  // 压缩比
  const rate = Math.max(inputSampleRate / outputSampleRate, 1),
    left: Float32Array = data.leftData,
    right: Float32Array = data.rightData,
    len: number = Math.floor((left.length + right.length) / (inputSampleRate / outputSampleRate)),
    dealResult: Float32Array = new Float32Array(len);

  for (let i: number = 0, j: number = 0; i < len; j += rate) {
    // 取整是因为存在比例rate不是整数的情况
    const temp: number = Math.floor(j);
    dealResult[i] = left[temp];
    i++;

    // 双声道处理
    // e.inputBuffer.getChannelData(0)得到了左声道4096个样本数据，1是右声道的数据，此处需要组和成LRLRLR这种格式，才能正常播放，所以要处理下
    if (right.length) {
      dealResult[i] = right[temp];
      i++;
    }
  }

  return dealResult
}

/**
 * 转换到我们需要的对应格式的编码
 *
 * @param bytes - pcm二进制数据
 * @param sampleBits - 采样位数
 * @param littleEdian - 是否是小端字节序
 */
const encodePCM = (
  data: Float32Array,
  sampleBits: 8 | 16,
  littleEdian: boolean = true,
): DataView => {
  const buffer: ArrayBuffer = new ArrayBuffer(data.length * (sampleBits / 8));
  const currentData: DataView = new DataView(buffer);
  if (sampleBits === 8)
    for (let i: number = 0, offset: number = 0; i < data.length; i++, offset++) {
      // 范围[-1, 1]
      const temp: number = Math.max(-1, Math.min(1, data[i]));
      // 8位采样位划分成2^8=256份，它的范围是0-255;
      // 对于8位的话，负数*128，正数*127，然后整体向上平移128(+128)，即可得到[0,255]范围的数据。
      let interval: number = temp < 0 ? temp * 128 : temp * 127;
      interval = +interval + 128;
      currentData.setInt8(offset, interval);
    }
  else
    for (let i: number = 0, offset: number = 0; i < data.length; i++, offset += 2) {
      const temp: number = Math.max(-1, Math.min(1, data[i]));
      // 16位的划分的是2^16=65536份，范围是-32768到32767
      // 因为我们收集的数据范围在[-1,1]，那么你想转换成16位的话，只需要对负数*32768,对正数*32767,即可得到范围在[-32768,32767]的数据。
      currentData.setInt16(offset, temp < 0 ? temp * 0x8000 : temp * 0x7FFF, littleEdian);
    }

  return currentData
}

/**
 * 编码wav，一般wav格式是在pcm文件前增加44个字节的文件头，
 * 所以，此处只需要在pcm数据前增加下就行了。
 * data属性为pcm二进制数据
 * inputSampleRate属性为输入采样率
 * outputSampleRate属性为输出采样率
 * channelsCount为声道数
 * sampleBits为输出采样位数
 * littleEdian是否是小端字节序
 */
const encodeWAV = (payload: {
  data: DataView;
  inputSampleRate: number;
  outputSampleRate: number;
  channelsCount: 1 | 2;
  sampleBits: 8 | 16;
  littleEdian?: boolean;
}): DataView => {
  payload.littleEdian = payload.littleEdian ?? true;
  const {
    data,
    inputSampleRate,
    outputSampleRate,
    channelsCount,
    sampleBits,
    littleEdian,
  } = payload;
  const currentData: DataView = new DataView(new ArrayBuffer(44 + data.byteLength)),
    // 输出采样率较大时，仍使用输入的值
    sampleRate: number = outputSampleRate > inputSampleRate ? inputSampleRate : outputSampleRate;
  let offset: number = 0;
  // 资源交换文件标识符
  writeString(currentData, offset, 'RIFF'); offset += 4;
  // 下个地址开始到文件尾总字节数,即文件大小-8
  currentData.setUint32(offset, 36 + data.byteLength, littleEdian); offset += 4;
  // WAV文件标志
  writeString(currentData, offset, 'WAVE'); offset += 4;
  // 波形格式标志
  writeString(currentData, offset, 'fmt '); offset += 4;
  // 过滤字节,一般为 0x10 = 16
  currentData.setUint32(offset, 16, littleEdian); offset += 4;
  // 格式类别 (PCM形式采样数据)
  currentData.setUint16(offset, 1, littleEdian); offset += 2;
  // 声道数
  currentData.setUint16(offset, channelsCount, littleEdian); offset += 2;
  // 采样率,每秒样本数,表示每个通道的播放速度
  currentData.setUint32(offset, sampleRate, littleEdian); offset += 4;
  // 波形数据传输率 (每秒平均字节数) 声道数 × 采样频率 × 采样位数 / 8
  currentData.setUint32(offset, channelsCount * sampleRate * (sampleBits / 8), littleEdian); offset += 4;
  // 快数据调整数 采样一次占用字节数 声道数 × 采样位数 / 8
  currentData.setUint16(offset, channelsCount * (sampleBits / 8), littleEdian); offset += 2;
  // 采样位数
  currentData.setUint16(offset, sampleBits, littleEdian); offset += 2;
  // 数据标识符
  writeString(currentData, offset, 'data'); offset += 4;
  // 采样数据总数,即数据总大小-44
  currentData.setUint32(offset, data.byteLength, littleEdian); offset += 4;

  // 给wav头增加pcm体
  for (let i: number = 0; i < data.byteLength; i++, offset++)
    currentData.setUint8(offset, data.getUint8(i));

  return currentData
}

export { compress, encodePCM, encodeWAV }